Skip to content

Conversation

@nojaf
Copy link
Member

@nojaf nojaf commented Nov 4, 2025

Use case: I want to compile a single ReScript file and run it immediately.
With this PR I could do rescript compile-file MyFile.res | bun - and bun automatically runs it.

(Would not be a stretch to create a Bun plugin and just be able to run bun run MyFile.res if plugin is configured)

Going to keep this as draft until after v12.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 4, 2025

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@8002

@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@8002

@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@8002

@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@8002

@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@8002

@rescript/runtime

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/runtime@8002

@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@8002

commit: 102f806

@nojaf
Copy link
Member Author

nojaf commented Nov 4, 2025

Split bsc module system flags for stdout compilation

Problem

When bsc compiles to stdout (no -bs-package-output specified), it hardcoded the Commonjs module system:

(* compiler/core/lam_compile_main.ml:295 - old code *)
if Js_packages_info.is_empty package_info && !Js_config.js_stdout then
  Js_dump_program.dump_deps_program ~output_prefix Commonjs (* <- hardcoded! *)

This meant stdout output was always Commonjs, regardless of project configuration. Since the new compile-file command outputs to stdout, it could only generate Commonjs, not ES6 modules.

Solution

Split -bs-package-output into three independent flags:

Before (coupled):

-bs-package-output esmodule:lib/es6:.mjs
# Format: module_system:path:suffix (all three or none)

After (decoupled):

-bs-module-system esmodule    # Controls import/export syntax
-bs-suffix .mjs              # Controls import path extensions  
-bs-package-output lib/es6   # Output directory (optional)

Implementation

New compiler state (compiler/common/js_config.ml):

let default_module_system = ref Ext_module_system.Commonjs
let default_suffix = ref Literals.suffix_js

New flags (compiler/bsc/rescript_compiler_main.ml):

("-bs-module-system", string_call set_module_system, "Set module system: commonjs, esmodule, es6-global");
("-bs-suffix", string_call set_suffix, "Set import file suffix: .js, .mjs, .cjs");

Stdout output now uses configured values (compiler/core/lam_compile_main.ml):

if Js_packages_info.is_empty package_info && !Js_config.js_stdout then
  (* Use configured module system instead of hardcoded Commonjs *)
  Js_dump_program.dump_deps_program ~output_prefix !Js_config.default_module_system

Import path generation uses configured suffix (compiler/core/js_name_of_module_id.ml):

(* For Package_script mode (stdout), use configured suffix *)
let js_file = Ext_namespace.js_name_of_modulename dep_module_id.id.name case !Js_config.default_suffix

Critical exception: Runtime package imports (@rescript/runtime) always use .js because the runtime is pre-compiled and distributed with .js files:

(* Runtime package is pre-compiled and always uses .js suffix *)
let js_file = Ext_namespace.js_name_of_modulename dep_module_id.id.name Upper Literals.suffix_js

Build System Updates

Rewatch (rewatch/src/build/compile.rs):

// Old format:
"-bs-package-output", "esmodule:lib/es6:.mjs"

// New format:
"-bs-module-system", "esmodule",
"-bs-suffix", ".mjs", 
"-bs-package-output", "lib/es6"

Legacy bsb (compiler/bsb/bsb_package_specs.ml):

(* Old: single compound flag *)
"-bs-package-output esmodule:lib/es6:.mjs"

(* New: three separate flags *)
"-bs-module-system esmodule -bs-suffix .mjs -bs-package-output lib/es6"

Backward Compatibility

The -bs-package-output parser still accepts the old format:

  • module:path:suffix - all three explicit (old format)
  • module:path - uses configured suffix
  • path - uses configured module system and suffix

This allows gradual migration, though since these are internal flags (users don't call bsc directly), backward compatibility isn't critical.

Result

bsc can now output ES6 modules to stdout:

$ bsc -bs-module-system esmodule -bs-suffix .mjs file.ast

// Generated by ReScript, PLEASE EDIT WITH CARE

import * as MyDep from "./MyDep.mjs";
import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";

export { ... }

Notice:

  • User module imports: .mjs (from -bs-suffix)
  • Runtime imports: .js (always, because runtime is pre-compiled)

This enables compile-file to respect project configuration when outputting to stdout.

//cc @cristianoc , @zth

nojaf added 8 commits November 4, 2025 19:50
The previous implementation used a ninja variable $in_d which doesn't exist,
causing files to be written to the wrong location. Now bsc extracts the
source subdirectory directly from the output_prefix parameter.

For in-source builds with output_prefix='src/Node', files are written to:
  project_root / '.' / 'src' / 'Node.mjs'

For out-of-source builds with output_prefix='src/Node', files go to:
  project_root / 'lib/es6' / 'src' / 'Node.mjs'

This ensures source directory structure is preserved in the output.
bsb passes path='.' and expects source_subdir to be extracted from output_prefix.
rewatch passes path=file_path.parent() which already contains the full directory.

Now check if path is a base directory (., lib/es6, etc.) and only then
extract source_subdir from output_prefix. Otherwise use path as-is.
…package-output

For in-source builds, extract source subdirectories from output_prefix and .cmj file locations to generate correct relative import paths.
@nojaf
Copy link
Member Author

nojaf commented Nov 5, 2025

Hi @cristianoc , the splitting of the bsc arg turned out quite ugly.
To satisfy bsb, a lot of code was generated while it seems a lot more straightforward in Rewatch.
Any pointers on what could be improved?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant